package com.sh.util;

import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.params.SetParams;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 分布式锁
 */
public class DLock implements AutoCloseable {

    static private final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DLock.class);

    /**
     * 本地缓存锁 减少IO
     */
    static private final Map<String, DLock> LOCK_MAP = new HashMap<String, DLock>();

    /**
     * 锁名称
     */
    private String _name;

    /**
     * 开始时间
     */
    private volatile long _startTime;
    /**
     * 持续时间
     */
    private volatile long _duration;
    /**
     * 是否已经解锁
     */
    private volatile boolean _unlocked;


    private DLock(String name) {
        _name = name;

    }

    /**
     * @param name     锁名称
     * @param duration 持续时间
     * @return 锁对象
     */
    static public DLock trylockAndGet(final String name, final long duration) {
        if (null == name ||
                name.isEmpty() ||
                duration <= 0) {
            return null;
        }

        autoClean();

        DLock newLock = new DLock(name);

        newLock._startTime = System.currentTimeMillis();
        newLock._duration = duration;

        //判断是否本地存在锁
        if (null != LOCK_MAP.putIfAbsent(name, newLock)) {
            LOGGER.warn("本地加锁失败！ name={}", name);
            return null;
        }

        try (Jedis redis = RedisUtil.getRedis()) {
            Pipeline pl = redis.pipelined();

            pl.set(name, "1", new SetParams().nx().px(duration));
            pl.pttl(name);
            List<Object> objList = pl.syncAndReturnAll();

            if (objList.size() < 2) {
                LOCK_MAP.remove(name);
                return null;
            }


            if ("ok".equalsIgnoreCase(String.valueOf(objList.get(0)))) {
                newLock._startTime = System.currentTimeMillis();
                newLock._unlocked = false;
                return newLock;
            } else {
                newLock._startTime = System.currentTimeMillis();
                newLock._duration = (long) objList.get(1);
                LOGGER.warn("Redis 加锁失败,将Redis锁同步到本地！ name={}", name);
                return null;
            }

        } catch (Exception ex) {
            LOGGER.error(ex.getMessage());
        }
        return null;
    }

    /**
     * 解锁
     */
    public void unlock() {
        if (_unlocked) {
            return;
        }
        _unlocked = true;

        try (Jedis redis = RedisUtil.getRedis()) {

            redis.del(_name);

        } catch (Exception ex) {
            LOGGER.error(ex.getMessage());
        }
        LOCK_MAP.remove(_name);


    }

    /**
     * 自动清理过期的锁
     */
    static private void autoClean() {
        long nowTime = System.currentTimeMillis();
        LOCK_MAP.values().removeIf((dLock) ->
                null != dLock && nowTime - dLock._startTime > dLock._duration
        );
    }

    @Override
    public void close() throws Exception {
        unlock();
    }
}
